/* Lepton EDA library
 * Copyright (C) 1998-2010 Ales Hvezda
 * Copyright (C) 1998-2016 gEDA Contributors
 * Copyright (C) 2017-2021 Lepton EDA Contributors
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
/*! \file line.c
 *
 *  \brief Low-level mathematical functions for lines
 */

#include <config.h>
#include <math.h>
#include <stdio.h>

#include "liblepton_priv.h"


/*! \brief Allocate a line
 *
 *  \return a pointer to an line, which must be freed with lepton_line_free.
 */
LeptonLine*
lepton_line_new ()
{
  return g_new0 (LeptonLine, 1);
}

/*! \brief Free memory associated with the line
 *
 *  \param [in] line the line to be freed
 */
void
lepton_line_free (LeptonLine *line)
{
  g_free (line);
}

/*! \brief Calculate the bounds of a line
 *
 *  Calculates the bounds of a line with zero width.
 *
 *  If this function fails, the bounds will be set to empty.
 *
 *  \param [in] line the line
 *  \param [out] bounds the bounds of the line
 */
void
lepton_line_calculate_bounds (const LeptonLine *line,
                              LeptonBounds *bounds)
{
  if (line == NULL) {
    lepton_bounds_init (bounds);
    g_return_if_fail (line != NULL);
  }

  lepton_bounds_init_with_points (bounds,
                                  line->x[0],
                                  line->y[0],
                                  line->x[1],
                                  line->y[1]);
}

/*! \brief Calculates the distance between the given point and the closest
 *  point on the given line segment.
 *
 *  If the closest point on the line resides beyond the line segment's
 *  end point, this function returns the distance from the given point to the
 *  closest end point.
 *
 *  If the line represents a single point (the endpoints are the same), this
 *  function calcualtes the distance to that point.
 *
 *  \param [in] line  The LeptonLine object.
 *  \param [in] x     The x coordinate of the given point.
 *  \param [in] y     The y coordinate of the given point.
 *  \return The shortest distance from the object to the point. With an
 *  invalid parameter, this function returns G_MAXDOUBLE.
 */
gdouble
lepton_line_shortest_distance (const LeptonLine *line,
                               gint x, gint y)
{
  double cx, cy;
  double dx, dy;
  double dx0, dy0;
  double lx0, ly0;
  double ldx, ldy;
  double t;

  g_return_val_if_fail (line != NULL, G_MAXDOUBLE);

  lx0 = (double)line->x[0];
  ly0 = (double)line->y[0];
  ldx = (double)(line->x[1] - line->x[0]);
  ldy = (double)(line->y[1] - line->y[0]);

  if (ldx == 0 && ldy == 0) {
    /* if line is a point, just calculate distance to the point */
    dx = x - lx0;
    dy = y - ly0;

  } else {
    /* calculate parametric value of perpendicular intersection */
    dx0 = ldx * (x - lx0);
    dy0 = ldy * (y - ly0);

    t = (dx0 + dy0) / (ldx * ldx + ldy * ldy);

    /* constrain the parametric value to a point on the line */
    t = MAX (t, 0);
    t = MIN (t, 1);

    /* calculate closest point on the line */
    cx = t * ldx + lx0;
    cy = t * ldy + ly0;

    /* calculate distance to closest point */
    dx = x - cx;
    dy = y - cy;
  }

  return hypot (dx, dy);
}
